﻿
using System;
namespace Spanner
{
    public partial class UI
    {
        /// <summary>
        /// An observable variable.  Changes to an observable will cause any attached
        /// subscriptions and 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="initialValue"></param>
        /// <returns></returns>
        public static ObsVar<T> ObsVar<T>(T initialValue = default(T)) { return ObsVar<T>(null, initialValue); }

        /// <summary>
        /// An observable variable.  Changes to an observable will cause any attached
        /// subscriptions and dependent computed variables to be re-evaluated.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="initialValue"></param>
        /// <returns></returns>
        public static ObsVar<T> ObsVar<T>(string jsName, T initialValue = default(T))
        {
            return new ObsVar<T> { InitialValue = (Expr<T>)initialValue, BaseName = jsName };
        }

        /// <summary>
        /// An observable variable.  Changes to an observable will cause any attached
        /// subscriptions and 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="initialValue"></param>
        /// <returns></returns>
        public static ObsVar<T> ObsVar<T>(Expr<T> initialValue)
        {
            return new ObsVar<T> { InitialValue = initialValue };
        }

        /// <summary>
        /// An observable variable.  Changes to an observable will cause any attached
        /// subscriptions and 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="initialValue"></param>
        /// <returns></returns>
        public static ObsVar<T> ObsVar<T>(string jsName, Expr<T> initialValue)
        {
            return new ObsVar<T> { InitialValue = initialValue, BaseName = jsName };
        }

        /// <summary>
        /// A computed variable.  Changes to any observable variable or other computed variable
        /// on which this variable depends will cause it to be re-evaluated.  This, in turn, may
        /// cause computed variables depending on this variable to be re-evaluated, and so forth.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="initialValue"></param>
        /// <returns></returns>
        public static ComputedVar<T> ComputedVar<T>(Expr<T> initialValue) { return ComputedVar<T>(null, initialValue); }

        /// <summary>
        /// A computed variable.  Changes to any observable variable or other computed variable
        /// on which this variable depends will cause it to be re-evaluated.  This, in turn, may
        /// cause computed variables depending on this variable to be re-evaluated, and so forth.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="initialValue"></param>
        /// <returns></returns>
        public static ComputedVar<T> ComputedVar<T>(string jsName, Expr<T> initialValue = null)
        {
            return new ComputedVar<T> { InitialValue = initialValue, BaseName = jsName };
        }

        /// <summary>
        /// An ordinary JavaScript variable.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="initialValue"></param>
        /// <returns></returns>
        public static Var<T> Var<T>(Expr<T> initialValue) { return Var<T>(null, initialValue); }

        /// <summary>
        /// An ordinary JavaScript variable.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="initialValue"></param>
        /// <returns></returns>
        public static Var<T> Var<T>(string jsName, Expr<T> initialValue)
        {
            return new Var<T> { Kind = VarKind.Var, InitialValue = initialValue, BaseName = jsName };
        }

        /// <summary>
        /// A named JavaScript function.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<T>> Function<T>(Func<Expr<T>> f) {
            return Var(Fn(f));
        }

        /// <summary>
        /// A named JavaScript function.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<T>> Function<T>(string jsName, Func<Expr<T>> f) {
            return Var(jsName, Fn(f));
        }

        /// <summary>
        /// A named JavaScript function.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, T>> Function<Ta, T>(Func<Param<Ta>, Expr<T>> f) {
            return Var(Fn(f));
        }

        /// <summary>
        /// A named JavaScript function.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, T>> Function<Ta, T>(string jsName, Func<Param<Ta>, Expr<T>> f) {
            return Var(jsName, Fn(f));
        }

        /// <summary>
        /// A named JavaScript function.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, T>> Function<Ta, Tb, T>(Func<Param<Ta>, Param<Tb>, Expr<T>> f) {
            return Var(Fn(f));
        }

        /// <summary>
        /// A named JavaScript function.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, T>> Function<Ta, Tb, T>(string jsName, Func<Param<Ta>, Param<Tb>, Expr<T>> f) {
            return Var(jsName, Fn(f));
        }

        /// <summary>
        /// A named JavaScript function.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, Tc, T>> Function<Ta, Tb, Tc, T>(Func<Param<Ta>, Param<Tb>, Param<Tc>, Expr<T>> f) {
            return Var(Fn(f));
        }

        /// <summary>
        /// A named JavaScript function.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, Tc, T>> Function<Ta, Tb, Tc, T>(string jsName, Func<Param<Ta>, Param<Tb>, Param<Tc>, Expr<T>> f) {
            return Var(jsName, Fn(f));
        }

        /// <summary>
        /// A named JavaScript function.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, Tc, Td, T>> Function<Ta, Tb, Tc, Td, T>(Func<Param<Ta>, Param<Tb>, Param<Tc>, Param<Td>, Expr<T>> f) {
            return Var(Fn(f));
        }

        /// <summary>
        /// A named JavaScript function.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, Tc, Td, T>> Function<Ta, Tb, Tc, Td, T>(string jsName, Func<Param<Ta>, Param<Tb>, Param<Tc>, Param<Td>, Expr<T>> f) {
            return Var(jsName, Fn(f));
        }

        /// <summary>
        /// A named JavaScript function with no return value (i.e., one executed for its side effect).
        /// </summary>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Act>> Procedure(Act f) {
            return Var(Proc(f));
        }

        /// <summary>
        /// A named JavaScript function with no return value (i.e., one executed for its side effect).
        /// </summary>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Act>> Procedure(string jsName, Act f) {
            return Var(jsName, Proc(f));
        }

        /// <summary>
        /// A named JavaScript function with no return value (i.e., one executed for its side effect).
        /// </summary>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Act>> Procedure<Ta>(Func<Param<Ta>, Act> f) {
            return Var(Proc(f));
        }

        /// <summary>
        /// A named JavaScript function with no return value (i.e., one executed for its side effect).
        /// </summary>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Act>> Procedure<Ta>(string jsName, Func<Param<Ta>, Act> f) {
            return Var(jsName, Proc(f));
        }

        /// <summary>
        /// A named JavaScript function with no return value (i.e., one executed for its side effect).
        /// </summary>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, Act>> Procedure<Ta, Tb>(Func<Param<Ta>, Param<Tb>, Act> f) {
            return Var(Proc(f));
        }

        /// <summary>
        /// A named JavaScript function with no return value (i.e., one executed for its side effect).
        /// </summary>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, Act>> Procedure<Ta, Tb>(string jsName, Func<Param<Ta>, Param<Tb>, Act> f) {
            return Var(jsName, Proc(f));
        }

        /// <summary>
        /// A named JavaScript function with no return value (i.e., one executed for its side effect).
        /// </summary>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, Tc, Act>> Procedure<Ta, Tb, Tc>(Func<Param<Ta>, Param<Tb>, Param<Tc>, Act> f) {
            return Var(Proc(f));
        }

        /// <summary>
        /// A named JavaScript function with no return value (i.e., one executed for its side effect).
        /// </summary>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, Tc, Act>> Procedure<Ta, Tb, Tc>(string jsName, Func<Param<Ta>, Param<Tb>, Param<Tc>, Act> f) {
            return Var(jsName, Proc(f)); }

        /// <summary>
        /// A named JavaScript function with no return value (i.e., one executed for its side effect).
        /// </summary>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, Tc, Td, Act>> Procedure<Ta, Tb, Tc, Td>(Func<Param<Ta>, Param<Tb>, Param<Tc>, Param<Td>, Act> f) {
            return Var(Proc(f));
        }

        /// <summary>
        /// A named JavaScript function with no return value (i.e., one executed for its side effect).
        /// </summary>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <param name="f"></param>
        /// <returns></returns>
        public static Var<Fn<Ta, Tb, Tc, Td, Act>> Procedure<Ta, Tb, Tc, Td>(string jsName, Func<Param<Ta>, Param<Tb>, Param<Tc>, Param<Td>, Act> f) {
            return Var(jsName, Proc(f));
        }

        /// <summary>
        /// Recursively defined functions must be first declared, then later defined in the
        /// initAct initialisation action for the owning model.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Var<T> DeclareVar<T>() { return Var<T>(default(T)); }

        /// <summary>
        /// Recursively defined functions must be first declared, then later defined in the
        /// initAct initialisation action for the owning model.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsName">The name this variable will be given in the generated JavaScript.</param>
        /// <returns></returns>
        public static Var<T> DeclareVar<T>(string jsName) { return Var<T>(jsName, default(T)); }

        /// <summary>
        /// Set the definition for a recursively defined function (see DeclareVar, which must be
        /// used first to declare such a function).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="v"></param>
        /// <param name="x"></param>
        public static void DefineVar<T>(Var<T> v, Expr<T> x)
        {
            var initialValue = v.InitialValue as ConstExpr<T>;

            if ((object)initialValue != null && initialValue.Const != null)
            {
                throw new ApplicationException("Vars cannot be redefined.");
            }

            v.InitialValue = x;
        }
    }
}
